﻿using System;
using System.Drawing;
using System.Windows.Forms;

namespace svs.Mobile.BeyondMedia.Controls
{
	public class DragHelper
	{
		#region Delegates

		public delegate void ClickedEvent(MouseEventArgs e, ClickSpeed speed);

		public delegate void DragEvent(DragDirection direction, DragSpeed speed);

		public delegate void MouseTouchEvent(MouseEventArgs e);

		#endregion

		#region ClickSpeed enum

		public enum ClickSpeed
		{
			Fast,
			Slow
		}

		#endregion

		#region DragDirection enum

		public enum DragDirection
		{
			N,
			NE,
			NW,
			S,
			SE,
			SW,
			E,
			W,
			None
		}

		#endregion

		#region DragSpeed enum

		public enum DragSpeed
		{
			None,
			Slow,
			Medium,
			Fast
		}

		#endregion

		private readonly Point _PrecisionWhileDragging = new Point(0, 0);
		private bool _IsDragging = false;

		private Point _LastMousePos;
		private long _LastMousePositionTime;
		private bool _MouseDown = false;
		private Point _MouseDownPos;
		private Point _PrecisionForClick = new Point(15, 15);
		private Timer _SlowClickTimer = new Timer();

		public Point Precision
		{
			get { return _PrecisionForClick; }
			set { _PrecisionForClick = value; }
		}

		public event ClickedEvent Click;
		public event MouseTouchEvent Touch;
		public event DragEvent Drag;

		public void OnMouseDown(Object o, MouseEventArgs e)
		{
			_MouseDownPos.X = e.X;
			_MouseDownPos.Y = e.Y;
			_LastMousePos = _MouseDownPos;
			_LastMousePositionTime = Environment.TickCount;
			_MouseDown = true;

			_SlowClickTimer.Interval = 1000;
			_SlowClickTimer.Tick += OnSlowClick;
			_SlowClickTimer.Enabled = true;

			if (Touch != null)
				Touch(e);
		}

		private void OnSlowClick(object sender, EventArgs e)
		{
			_SlowClickTimer.Enabled = false;
			OnMouseUp(null, null);
		}

		public void OnMouseMove(Object o, MouseEventArgs e)
		{
			if (!_MouseDown)
				return;

			Point newPoint = new Point(e.X, e.Y);

			DragDirection dragDir =
				IsDragging(_LastMousePos, newPoint, _IsDragging ? _PrecisionWhileDragging : _PrecisionForClick);
			DragSpeed dragSpeed = CalculateDragSpeed(_LastMousePos, newPoint, _LastMousePositionTime);

			if (dragDir != DragDirection.None)
			{
				_IsDragging = true;
				if (Drag != null)
					Drag(dragDir, dragSpeed);

				_LastMousePos = newPoint;
				_LastMousePositionTime = Environment.TickCount;
			}
		}

		private static DragSpeed CalculateDragSpeed(Point lastP, Point newP, long lastTime)
		{
			int diffX = newP.X - lastP.X;
			int diffY = newP.Y - lastP.Y;

			if (diffX < 0)
				diffX *= -1;

			if (diffY < 0)
				diffY *= -1;

			int distance = Math.Max(diffX, diffY);

			DragSpeed retVal;

			if (distance <= 50)
				retVal = DragSpeed.Slow;
			else if (distance > 50 && distance <= 100)
				retVal = DragSpeed.Medium;
			else
				retVal = DragSpeed.Fast;

			return retVal;
		}

		public void OnMouseUp(Object o, MouseEventArgs e)
		{
			if (!_MouseDown)
				return;

			if (!_IsDragging && Click != null)
			{
				ClickSpeed speed = _SlowClickTimer.Enabled ? ClickSpeed.Fast : ClickSpeed.Slow;
				Click(e, speed);
			}

			_SlowClickTimer.Enabled = _MouseDown = _IsDragging = false;
		}

		private static DragDirection IsDragging(Point lastP, Point newP, Point precision)
		{
			int diffX = newP.X - lastP.X;
			int diffY = newP.Y - lastP.Y;

			if ((diffX < 0 ? (diffX*-1) : diffX) <= precision.X)
				diffX = 0;

			if ((diffY < 0 ? (diffY*-1) : diffY) <= precision.Y)
				diffY = 0;

			if (diffX > 0 && diffY == 0)
				return DragDirection.E;
			else if (diffX < 0 && diffY == 0)
				return DragDirection.W;
			else if (diffX == 0 && diffY > 0)
				return DragDirection.S;
			else if (diffX == 0 && diffY < 0)
				return DragDirection.N;
			else if (diffX > 0 && diffY > 0)
				return DragDirection.SE;
			else if (diffX < 0 && diffY < 0)
				return DragDirection.NW;
			else if (diffX < 0 && diffY > 0)
				return DragDirection.SW;
			else if (diffX > 0 && diffY < 0)
				return DragDirection.NE;

			return DragDirection.None;
		}
	}
}